package com.server.transaction.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.server.common.exception.ErrorCode;
import com.server.common.exception.ServerException;
import com.server.framework.utils.JsonUtils;
import com.server.transaction.domain.dto.SecureInvokeDTO;
import com.server.transaction.domain.entity.SysSecureRecord;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * @title: SecureInvokeHandle
 * @author: gyuan
 * @description
 * @date: 2023-11-16 18:24
 */
@Slf4j
@AllArgsConstructor
public class SecureInvokeHandle {

    public static final int RETRY_INTERVAL_MINUTES = 2;

    private SecureInvokeRecordService secureInvokeRecordService;

    private Executor executor;

    @Scheduled(cron = "*/5 * * * * ?")
    public void retry() {
        List<SysSecureRecord> invokeRecords = secureInvokeRecordService.lambdaQuery()
                .eq(SysSecureRecord::getStatus, SysSecureRecord.STATUS_WAIT)
                .lt(SysSecureRecord::getNextRetryTime, new Date())
                .lt(SysSecureRecord::getCreateTime, DateUtil.offsetMinute(new Date(), RETRY_INTERVAL_MINUTES))
                .list();
        for (SysSecureRecord sysSecureRecord : invokeRecords) {
            doInvoke(sysSecureRecord);
        }
    }

    public Object invoke(SysSecureRecord invokeRecord, boolean async) {
        secureInvokeRecordService.save(invokeRecord);
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                if (async) {
                    doAsyncInvoke(invokeRecord);
                } else {
                    doInvoke(invokeRecord);
                }
                ;
            }
        });
        return null;
    }

    private void doAsyncInvoke(SysSecureRecord invokeRecord) {
        executor.execute(() -> {
            log.info("async invoke transaction task");
            doInvoke(invokeRecord);
        });
    }

    private void doInvoke(SysSecureRecord invokeRecord) {
        SecureInvokeDTO secureInvokeDTO = invokeRecord.getSecureInvokeDTO();
        try {
            // 在同步执行时，由于仍在同一线程内，导致第二次进入aop切面 !TransactionSynchronizationManager.isActualTransactionActive() 判断有误
            SecureInvokeHolder.setInvoking();
            Class<?> aClass = Class.forName(secureInvokeDTO.getClassName());
            Object obj = SpringUtil.getBean(aClass);
            List<String> parameterTypeList = JsonUtils.parseArray(secureInvokeDTO.getParameterTypes(), String.class);
            List<Class<?>> parameterTypeClassList = getParameter(parameterTypeList);
            Object[] args = getArgs(parameterTypeClassList, secureInvokeDTO.getArgs());
            Method method = ReflectUtil.getMethod(aClass, secureInvokeDTO.getMethodName(), parameterTypeClassList.toArray(new Class[]{}));
            Object result = method.invoke(obj, args);

            String rollbackClass = invokeRecord.getRollbackClass();
            // 先从spring中获取bean，获取不到时再new
            Map<String, ?> beanMap = SpringUtil.getBeansOfType(Class.forName(rollbackClass));
            CheckHandle rollback = (beanMap == null || beanMap.isEmpty()) ? (CheckHandle) ReflectUtil.newInstance(Class.forName(rollbackClass)) : (CheckHandle) beanMap.values().toArray()[0];
            if (!rollback.check(result)) {
                throw new ServerException(ErrorCode.RESULT_CHECK_ERROR);
            }
            // 执行成功后删除记录
            secureInvokeRecordService.removeById(invokeRecord.getId());
        } catch (Exception e) {
            log.error("execute transaction method {}#{} error ", secureInvokeDTO.getClassName(), secureInvokeDTO.getMethodName(), e);
            Integer times = invokeRecord.getRetryTimes();
            if (times >= invokeRecord.getMaxRetryTimes()) {
                invokeRecord.setStatus(SysSecureRecord.STATUS_FAIL);
            } else {
                invokeRecord.setRetryTimes(times + 1);
                invokeRecord.setNextRetryTime(getNextRetryTime(invokeRecord.getRetryTimes()));
            }
            invokeRecord.setFailReason(e.getMessage());
            secureInvokeRecordService.updateById(invokeRecord);
        } finally {
            SecureInvokeHolder.invoked();
        }
    }

    private Date getNextRetryTime(Integer retryTime) {
        // 2m 4m 8m 重试时间指数上升
        double interval = Math.pow(RETRY_INTERVAL_MINUTES, retryTime);
        return DateUtil.offsetMinute(new Date(), (int) interval);
    }

    private Object[] getArgs(List<Class<?>> parameterTypeClassList, String args) {
        JsonNode jsonNode = JsonUtils.toJsonNode(args);
        Object[] objects = new Object[jsonNode.size()];
        for (int i = 0; i < jsonNode.size(); i++) {
            Class<?> aClass = parameterTypeClassList.get(i);
            objects[i] = JsonUtils.nodeToValue(jsonNode.get(i), aClass);
        }
        return objects;
    }

    private List<Class<?>> getParameter(List<String> parameterTypeList) {
        return parameterTypeList.stream()
                .map(name -> {
                    try {
                        return Class.forName(name);
                    } catch (ClassNotFoundException e) {
                        log.error("class {} not found ", name, e);
                    }
                    return null;
                })
                .collect(Collectors.toList());
    }

}